#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Purpose: Node type editor dialog
# Created: 01/30/2009
# $Id $
# Copyright (c) 2008,2009 by University College London
# Authors:
# Didrik Pinte --<dpinte@dipole-consulting.com>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with HydroPlatform for details.

import logging
import wx
import wx.lib.sheet
from wx.lib.intctrl import IntCtrl
from sqlalchemy import exc

from Thuban.Lib.connector import Publisher

from enthought.traits.api import DelegationError

from hydroplatform.model.network import NodeField, LinkField, NodeType, \
                        LinkType, FieldGroup, \
                        PROJECT_NODETYPE_ADDED
from hydroplatform.model import database as db
from hydroplatform.model.networkfun import copy_objecttype_as
from hydroplatform.model import networkfun
from hydroplatform.model.nodetypes import DEFAULT_GROUP_NAME
from hydroplatform.ui import fields as fd
from hydroplatform.ui import renderer
from hydroplatform.ui.resources import get_image_resource


CHOOSE_NODE_TYPE = u"Choose a node type"
CHOOSE_LINK_TYPE = u"Choose a link type"

logging.basicConfig()

def integrity_errormesg( exception):
    '''
    Facilty functions to print error messages due to an
    sqlalchemy.exc.IntegritError
    
    FIXME: should be moved to a more appropriate module
    '''
    assert isinstance(exception, exc.IntegrityError)
    logging.warn(exception)
    wx.MessageBox(u'''Integrity error while flushing your objecttype.\n
It is most probably because the name of your objecttype already
exist in database for the loaded project.\n
If you try to delete a field and recreate it, you have to do it in two steps:\n
First delete the field and save the object type. Second create the new field !
''' , u"Error while saving")   


class InvalidFieldException(Exception):
    pass


class ObjectTypeEditorDialog(wx.Frame, Publisher):
    '''
    This frame must show a notebook with a list of panels corresponding to :
    - the general info of the selected object type
    - the fields of the selected object type
    - the groups available for the given object type
    - the links page is only shown for node types
    '''
    def __init__(self, parent,  context):
        wx.Frame.__init__(self, parent, -1, "Object type editor",  
                          style = wx.DEFAULT_FRAME_STYLE,
                          size=wx.Size(800, 600)
                        )
        self.parent = parent
        self.SetSizeHints(800, 600)
        
        self.name = "node_type_editor"
        

        self.context = context
        dbsession = context.session.dbsession
        
        self.default_group = dbsession.query(db.FieldGroup).filter_by(\
            name=DEFAULT_GROUP_NAME).first()
        if self.default_group is None:
            self.default_group = FieldGroup(DEFAULT_GROUP_NAME)
            dbsession.add(self.default_group)
            session.dbsession.commit()
            print "Default group created"
        else:
            print "Default group already existing"
        print self.default_group
        
        # build a dictionnary of nodetypes with object as key and name as value
        self.nodetypes = dict()    
        self.linktypes = dict()    
        for nodetype in self.context.session.hpproject.nodetypes:
            self.nodetypes[nodetype.name] = nodetype
        
        for linktype in self.context.session.hpproject.linktypes:
            self.linktypes[linktype.name] = linktype
            
        # create controls
        self.boxsizer = wx.BoxSizer(wx.VERTICAL)
        #self.toolbar = wx.ToolBar(self.panel, wx.TB_HORIZONTAL)
        self.toolbar = self.CreateToolBar()
        self.toolbar.SetToolBitmapSize((25, 25))
        
        # Node type combo box
        nodetype_keys = self.nodetypes.keys()
        nodetype_keys.sort()
        self.nodetypes_cb = wx.ComboBox(self.toolbar, -1, 
                                        value=CHOOSE_NODE_TYPE,
                                choices=[CHOOSE_NODE_TYPE] + nodetype_keys, 
                                size=(200, -1),
                                style=wx.CB_DROPDOWN | wx.CB_READONLY)
        
        # Link type combo box
        linktype_keys = self.linktypes.keys()
        linktype_keys.sort()
        self.linktypes_cb = wx.ComboBox(self.toolbar, -1, 
                                        value=CHOOSE_LINK_TYPE,
                                choices=[CHOOSE_LINK_TYPE] + linktype_keys, 
                                size=(200, -1),
                                style=wx.CB_DROPDOWN | wx.CB_READONLY) 
        
        # Action buttons
        
        # Add node type
        self.add_nodetype_bmp = get_image_resource("add_nodetype", 
                                    wx.BITMAP_TYPE_PNG).ConvertToBitmap()
        self.add_type_btn = wx.BitmapButton(self.toolbar, -1, 
                                            self.add_nodetype_bmp)
        self.add_type_btn.SetToolTip(wx.ToolTip(u"Add new node type"))
        
        # Add link type
        self.add_linktype_bmp = get_image_resource("add_linktype", 
                                    wx.BITMAP_TYPE_PNG).ConvertToBitmap()
        self.add_linktype_btn = wx.BitmapButton(self.toolbar, -1, 
                                                self.add_linktype_bmp)
        self.add_linktype_btn.SetToolTip(wx.ToolTip(u"Add new link type"))
        
        # Save object type
        self.save_nodetype_bmp =  get_image_resource("document-save",
                                    wx.BITMAP_TYPE_PNG).ConvertToBitmap()
        self.save_type_btn = wx.BitmapButton(self.toolbar, -1, 
                                             self.save_nodetype_bmp, 
                                             size=(35,35))
        self.save_type_btn.SetToolTip(
            wx.ToolTip(u"Saves your changes to the loaded object type"))
        
        # Save as object type
        self.saveas_nodetype_bmp =  get_image_resource("document-save-as",
                                    wx.BITMAP_TYPE_PNG).ConvertToBitmap()
        self.saveas_type_btn = wx.BitmapButton(self.toolbar, -1, 
                                             self.saveas_nodetype_bmp, 
                                             size=(35,35))
        self.saveas_type_btn.SetToolTip(
            wx.ToolTip(u"Saves the loaded object type as a new type"))        
        
        # Delete object type
        self.delete_nodetype_bmp =  get_image_resource("delete_object",
                                    wx.BITMAP_TYPE_PNG).ConvertToBitmap()
        self.delete_type_btn = wx.BitmapButton(self.toolbar, -1, 
                                             self.delete_nodetype_bmp, 
                                             size=(35,35))
        self.delete_type_btn.SetToolTip(
            wx.ToolTip(u"Delete the loaded object type")) 
        
        self.notebook = wx.Notebook(self, -1)
        
        # add controls to the toolbar if needed
        assert isinstance(self.toolbar, wx.ToolBar)
        self.toolbar.AddControl(self.add_type_btn)
        self.toolbar.AddControl(self.nodetypes_cb)
        self.toolbar.AddSeparator()
        self.toolbar.AddControl(self.add_linktype_btn)
        self.toolbar.AddControl(self.linktypes_cb)
        self.toolbar.AddSeparator()
        self.toolbar.AddControl(self.save_type_btn)
        self.toolbar.AddControl(self.saveas_type_btn)
        self.toolbar.AddSeparator()
        self.toolbar.AddControl(self.delete_type_btn)
        self.toolbar.Realize()
        
        
        # event binding
        self.Bind(wx.EVT_CLOSE, self.on_close)
        self.Bind(wx.EVT_COMBOBOX, self.on_choice, self.nodetypes_cb)
        self.Bind(wx.EVT_COMBOBOX, self.on_choice, self.linktypes_cb)
        self.Bind(wx.EVT_BUTTON, self.on_save, self.save_type_btn)
        self.Bind(wx.EVT_BUTTON, self.on_saveas, self.saveas_type_btn)
        self.Bind(wx.EVT_BUTTON, self.on_delete, self.delete_type_btn)
        self.Bind(wx.EVT_BUTTON, self.on_addnodetype, self.add_type_btn)
        self.Bind(wx.EVT_BUTTON, self.on_addlinktype, self.add_linktype_btn)
        
        #self.panel.SetSizer(self.boxsizer)

        # Manage events that will be issued from this class
        self.issue_add_node_type = False
        self.issue_remove_node_type = False
        try:
            nodetype_menu =  context.application.get_nodetype_menu(context)
            self.Subscribe(PROJECT_NODETYPE_ADDED, \
                           nodetype_menu.add_nodetype)
        except AttributeError, exception:
            logging.warn("No way to subscribe to the nodetype menu : %s", \
                         exception)
        
        # create the status bar
        self.CreateStatusBar()      
        
     
    def on_close(self, event):
        '''
        Destroys the windows nicely when closed
        '''
        self.Destroy()    
        
        
    def update_name_and_desc(self):
        """
        Update name and description to objecttype
        """
        # update selected node for general content
        self.objecttype.name = self.name_txt.GetValue() 
        self.objecttype.description = self.desc_txt.GetValue()
        if isinstance(self.objecttype, NodeType):
            self.objecttype.visible_on_map = self.visible_checkbox.GetValue()
        
    def on_saveas(self, event):
        """
        Save the current object as a new type
        """
        if not hasattr(self, 'objecttype'):
            return
 
        # first, save the current object if needed
        self.on_save(event)
        # then save it to a new object type and ask name
        dialog = wx.TextEntryDialog(self,
                        "Object type name",
                        "Save object type as")
        if dialog.ShowModal() == wx.ID_OK:
            name = dialog.GetValue() 
            copy_objecttype_as(self.context.session.dbsession,
                               self.context.session.hpproject,
                               self.objecttype,
                               name)
        self.refresh_listboxes()
        
    def on_save(self, event):
        '''
        Takes care of saving all the changes made to the loaded objects (if any)
        '''
        if not hasattr(self, 'objecttype'):
            return
        self.update_name_and_desc()
        # save the object
        dbsession = self.context.session.dbsession
        try:
            # since sqlalchemy 0.5.3, no need to flush the object
            #dbsession.flush([self.objecttype])
            dbsession.flush()
            # synchronize traits fields with db fields
            try:
                self.sync_fields()
            except InvalidFieldException, e:
                wx.MessageBox(e.message, "Error while synchronizing your object type",
                              style=wx.OK | wx.CENTER | wx.ICON_ERROR)
                return
            
            # commit the changes 
            dbsession.commit()
            # refresh the session
            # FIXME : is this needed ?
            dbsession.refresh(self.context.session.hpproject)
            dbsession.refresh(self.objecttype)
            # update the fid for the newly created traits
            for traitfield in self.objectfields.fields:
                if traitfield.fid == -1 :
                    # update it to the db val
                    for dbfield in self.objecttype.fields:
                        if dbfield.name == traitfield.name :
                            traitfield.fid = dbfield.id
            # refresh listbox content
            self.refresh_listboxes()
            # issue a PROJECT_NODETYPE_ADDED message 
            # (links are not managed in the same way)
            if self.issue_add_node_type is True \
               and isinstance(self.objecttype, NodeType):
                self.issue(PROJECT_NODETYPE_ADDED, self.objecttype)
                self.issue_add_node_type = False
            elif isinstance(self.objecttype, NodeType):
                # node is not new, update its menu's and actions
                try:                    
                    nodetype_menu = self.context.application.get_nodetype_menu(self.context)
                    nodetype_menu.update_nodetype(self.objecttype)
                except AttributeError, exception:
                    logging.warn("No way to subscribe to the nodetype menu : %s", \
                             exception)
        
        
            wx.MessageBox("Object type saved successfully", 
                          "Save object type modification")
        except exc.IntegrityError, exception:
            dbsession.rollback()
            integrity_errormesg(exception)
        except Exception, exception:
            logging.exception(exception)
            dbsession.rollback()
            raise exception

    def on_delete(self, event):
        """
        Delete the selected objecttype
        """
        if hasattr(self, 'objecttype') and self.objecttype is not None:
            dlg = wx.MessageDialog(self, \
u""" Are your sure you want to delete the %s object type. 
This will erase it from the database and all the associated information""" \
                    % self.objecttype.name,
                     u"Object type deletion",
                     style=wx.OK| wx.CANCEL| wx.CENTER | wx.ICON_EXCLAMATION)
            res = dlg.ShowModal()
            if res == wx.ID_OK:
                logging.info("Deleting %s" % self.objecttype.name)
                dbsession = self.context.session.dbsession
                project = self.context.session.hpproject
                if isinstance(self.objecttype, db.NodeType):
                    # if node type, needs to refresh the toolbar
                    try:
                        nodetype_menu =  self.context.application.get_nodetype_menu(self.context)
                        nodetype_menu.remove_nodetype(self.objecttype)
                    except AttributeError, exception:
                        pass
                    
                networkfun.delete_objecttype_from_project(dbsession, project, \
                                                          self.objecttype) 
                
                dbsession.refresh(self.context.session.hpproject)
                self.refresh_listboxes()
                self.notebook.DeleteAllPages()
                
            return

    
    def on_addlinktype(self, event):
        '''
        Method called to add a new link type
        '''
        # reset the combo states
        self.nodetypes_cb.SetStringSelection(CHOOSE_NODE_TYPE)
        self.linktypes_cb.SetStringSelection(CHOOSE_LINK_TYPE)
        # create the new object 
        self.objecttype = LinkType(u"Undefined", None, 
                                   self.context.session.hpproject)
        self.context.session.dbsession.add(self.objecttype)        
        # add default group to this new type
        self.objecttype.groups.append(self.default_group)
        # show properties
        self.show_object()
   
    def on_addnodetype(self, event):
        '''
        Method called to add a new node type
        '''
        # reset the combo states
        self.nodetypes_cb.SetStringSelection(CHOOSE_NODE_TYPE)
        self.linktypes_cb.SetStringSelection(CHOOSE_LINK_TYPE)
        # create the new object
        self.objecttype = NodeType(u"Undefined", None, 
                                   self.context.session.hpproject)
        self.context.session.dbsession.add(self.objecttype)
        self.objecttype.groups.append(self.default_group)
        # on save must issue events
        self.issue_add_node_type = True
        # show properties
        self.show_object()
        
    def refresh_listboxes(self):
        ''' Refresh the two combo boxes listing linktypes and nodetypes
        '''
        linktype_value = self.linktypes_cb.GetValue() 
        nodetype_value = self.nodetypes_cb.GetValue()
        
        # purge dictionnaries in case some objecttype where removed
        self.nodetypes.clear()
        self.linktypes.clear()
        
        # refresh the nodetypes and linktypes dictionnaries
        for nodetype in self.context.session.hpproject.nodetypes:
            self.nodetypes[nodetype.name] = nodetype
        
        for linktype in self.context.session.hpproject.linktypes:
            self.linktypes[linktype.name] = linktype
        
        # Node type combo box
        nodetype_keys = self.nodetypes.keys()
        nodetype_keys.sort()
        self.nodetypes_cb.Clear()
        self.nodetypes_cb.AppendItems([CHOOSE_NODE_TYPE] + nodetype_keys)
        if nodetype_value is not None and \
           self.nodetypes.has_key(nodetype_value):
            self.nodetypes_cb.SetValue(nodetype_value)
        
        # Link type combo box
        linktype_keys = self.linktypes.keys()
        linktype_keys.sort()
        self.linktypes_cb.Clear()
        self.linktypes_cb.AppendItems([CHOOSE_LINK_TYPE] + linktype_keys)
        if linktype_value is not None and \
           self.linktypes.has_key(linktype_value):
            self.linktypes_cb.SetValue(linktype_value)
    

    def sync_fields(self):
        '''
        Synchronize the traits NodeFields in memory with the db fields
        '''
        updated = False
        if isinstance(self.objecttype, NodeType):
            clsNewField = NodeField
        elif isinstance(self.objecttype, LinkType):
            clsNewField = LinkField
        # check the field content agains the db
        dbfields = dict([(dbfield.id, dbfield) for dbfield \
                         in self.objecttype.fields])
        #check if some nodes have been added
        for traitfield in self.objectfields.fields:                
            if traitfield.finst is None:
                raise InvalidFieldException(u"Every field of your object type must have a valid type")
 
            # if id is -1, means not existing in DB
            if traitfield.fid == -1:
                # get groupid from objecttype.groups
                for grp in self.objecttype.groups:
                    if grp.name == traitfield.group:
                        break
                else:
                    # no grp
                    raise Exception("No group with name %s for this node type" 
                                    % traitfield.group)
                try:
                    traitfield._configupdated = False
                    # add new field in database
                    dbfield = clsNewField(self.objecttype.id, 
                                        traitfield.finst, 
                                        traitfield.name, 
                                        grp)
                    self.context.session.dbsession.add(dbfield)
                    updated = True
                except DelegationError, e:
                    logging.warn(e)        # check if some fields have been removed
        for dbfield in dbfields.values():
            if dbfield.id not in [f.fid for f in self.objectfields.fields]:
                # erased field
                # FIXME :add confirmation 
                # and delete all values for the given field
                self.context.session.dbsession.delete(dbfield)
                self.context.session.dbsession.flush()
                print "field deleted"
                updated = True

        # check if some fields have been modified
        for traitfield in self.objectfields.fields:
            # existing field
            if traitfield._configupdated is True:
                print "Fields %s updated" % traitfield.name
                dbfield = dbfields[traitfield.fid]
                # field changed
                # update db field accordingly
                dbfield.attribute_type = traitfield.finst.clone_traits()
                # get group from objecttype
                dbgrp = None
                for group in self.objecttype.groups:
                    if group.name == traitfield.group:
                        dbgrp = group
                if dbgrp is not None:
                    dbfield.group = dbgrp
                else:
                    raise Exception("No group in database for selected group in editor")
                dbfield.name = traitfield.name
                self.context.session.dbsession.commit()
                self.context.session.dbsession.flush()
                traitfield._configupdated = False 
                updated = True
                
        return updated
                
    def on_choice(self, event):
        '''
        Manages a change in the two listboxes used for nodes and links.
        Whenever the user changes one of the two listboxes, it updates
        the notebook accordingly
        '''
    
        if self.notebook.GetPageCount() > 0:
            dbsession = self.context.session.dbsession
            
            # update selected node for general content
            if self.objecttype.name != self.name_txt.GetValue() or\
               self.objecttype.description != self.desc_txt.GetValue():
                self.objecttype.name = self.name_txt.GetValue() 
                self.objecttype.description = self.desc_txt.GetValue()
                # flush it to DB
                try:
                    dbsession.flush()
                except exc.IntegrityError, exception :
                    dbsession.rollback()
                    # reinsert the objecttype in the session as it has been 
                    # removed due to the rollback()
                    dbsession.add(self.objecttype)
                    # show the error message
                    integrity_errormesg(exception)
                    return 
            
            # check if user made changes
                        
            try:
                is_modified = self.sync_fields()
            except InvalidFieldException, e:
                wx.MessageBox(e.message, "Error while synchronizing your object fields",
                              style=wx.OK | wx.CENTER | wx.ICON_ERROR)
                return
            
            print "Is session modified ? %s " % is_modified
                    
            
            if is_modified == False and dbsession.is_modified(self.objecttype):
                is_modified = True
            else:
                if hasattr(self.objecttype, 'modified'):
                    is_modified = True
                    del self.objecttype.modified
            if is_modified is True :
                #ask the user to confirm saving the changes
                dlg = wx.MessageDialog(self,  \
                    u"Do you want to save the changes to this object type ?", \
                    u"Object type has been modified", \
                    wx.YES_NO | wx.ICON_QUESTION)
                return_code = dlg.ShowModal()
                if return_code == wx.ID_YES:
                    dbsession.commit()
                    dbsession.refresh(self.context.session.hpproject)
                    # refresh listbox content
                    self.refresh_listboxes()
                    if self.issue_add_node_type is True \
                       and isinstance(self.objecttype, NodeType):
                        self.issue(PROJECT_NODETYPE_ADDED, self.objecttype)
                        self.issue_add_node_type = False
                else: 
                    dbsession.rollback()                
            
        if event.GetEventObject() is self.linktypes_cb:
            # User chooses a link type
            try:
                self.nodetypes_cb.SetStringSelection(CHOOSE_NODE_TYPE)
                self.objecttype = self.linktypes[self.linktypes_cb.GetValue()]
            except KeyError:
                return
        else :
            # User chooses a node type
            try:
                self.linktypes_cb.SetStringSelection(CHOOSE_LINK_TYPE)
                # retrieve the index of the choosen nodetype name
                self.objecttype = self.nodetypes[self.nodetypes_cb.GetValue()]
            except KeyError:
                return
            
        self.show_object()
   
    
    def show_object(self):
        '''
        Loads the information related to the selection self.objecttype
        into the UI
        '''
        # remove all pages from previous node type
        self.notebook.DeleteAllPages() 
        # add a general page
        self.general = wx.Panel(self.notebook)
        self.set_generalpage_layout(self.general)
        try:
            self.objecttype.groups.index(self.default_group) 
        except ValueError:
            #register default group
            self.objecttype.groups.append(self.default_group)
            
        self.group_list = {}
        for group in self.objecttype.groups:
            self.group_list[group.name] = group
        
        glist = [str(v) for v in self.group_list.keys() ]
       
        
        # feed the NodeFields using the existing fields in db
        self.objectfields =  fd.ObjectFields(groups=glist, 
                                             project=self.context.session.hpproject)
        # list the existing fields for the selected nodetype
        for field in self.objecttype.fields:
            fid = fd.ObjectField(fid = field.id,
                            name = field.name,
                            group = field.group.name,
                            ftype = fd.get_name_for_class(field.attribute_type),
                            finst = field.attribute_type,
                            parent = self.objectfields)
            self.objectfields.fields.append(fid)
        
        self.fields_page = self.objectfields.edit_traits(kind="subpanel", 
                            parent=self.notebook,
                            view = self.objectfields.getObjectFieldsView()
                            ).control
        
        groups = DataGroupPanel(self.notebook, 
                                self.context, 
                                self.objecttype, 
                                self.objectfields.groups)
        
        
        self.notebook.AddPage(self.fields_page, u"Fields", False)
        self.notebook.AddPage(groups, u"Data groups", False)
        self.notebook.InsertPage(0, self.general, u"General", True)         
        # The Links page is hidden for the moment
        #if isinstance(self.objecttype, NodeType):
        #    logging.info("Adding links page")
        #    self.links = LinksChooserPanel(self.notebook, 
        #                      self.context, self.objecttype)
        #    self.notebook.AddPage(self.links, u"Links", False)
                
       
    
    def on_editsymbol(self, event):
        '''
        Open the SymbolEditorDialog associated with this objecttype
        '''
        symbolframe = SymbolEditorDialog(self, self.objecttype)
        symbolframe.Show()
        
    def update_symbolbtn(self):
        '''
        Update the bitmap of the symbol_btn using the nodetype bmp
        '''
        self.symbol_btn.SetBitmapLabel(self.get_bmp_nodetype())
        self.Update()
        #self.Fit()
        
    def get_bmp_nodetype(self):
        '''
        Returns a wx.Bitmap object for the loaded objecttype
        '''
        if isinstance(self.objecttype, NodeType):
            pen = wx.Pen(self.objecttype.pen)
            brush = wx.Brush(self.objecttype.brush)
            shape = self.objecttype.shape
            size = self.objecttype.size
        else:
            pen = wx.Pen(self.objecttype.pen,
                         width=self.objecttype.size,
                         style=eval(self.objecttype.style))
            brush = None
            shape = "line"        
            size = 10
        print self.objecttype.size
            
        bmp =  renderer.getBitmapForShape(shape, 
                      size=size, 
                      pen=pen, brush=brush)
        return bmp
    
    def set_generalpage_layout(self, panel):
        '''
        General page show a panel with for an object type.
        
        If it is a node type : show the name, symbol and description
        If it is a link type : show the name and description
        '''
        name_static    = wx.StaticText(panel, -1, u"Type name :")
        self.name_txt  = wx.TextCtrl(panel, -1, self.objecttype.name)        
        
        if isinstance(self.objecttype, NodeType):
            visible_static    = wx.StaticText(panel, -1, u"Visible on map :")
            self.visible_checkbox  = wx.CheckBox(panel, -1)
            # FIME : due to the change in the data model. The  visible_on_map attribute may not be there ...
            if not hasattr(self.objecttype, 'visible_on_map') or self.objecttype.visible_on_map is None:
                self.objecttype.visible_on_map = True 
            self.visible_checkbox.SetValue(self.objecttype.visible_on_map)  

        symbol_static = wx.StaticText(panel, -1, u"Symbol :")
        self.symbol_btn = wx.BitmapButton(panel, -1, 
                                          self.get_bmp_nodetype())
        
        desc_static = wx.StaticText(panel, -1, u"Description :")
        desc_value = u""
        if self.objecttype.description is not None:
            desc_value = self.objecttype.description
        self.desc_txt = wx.TextCtrl(panel, -1, desc_value, size=(450, 200), 
                                    style=wx.TE_MULTILINE)
        
        sizer = wx.FlexGridSizer(cols=2, hgap=6, vgap=6)
        sizer.AddGrowableCol(1)
        sizer.Add(name_static, 0, wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 5)
        sizer.Add(self.name_txt, 1, wx.EXPAND)
        if isinstance(self.objecttype, NodeType):
            sizer.Add(visible_static, 0, wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 5)
            sizer.Add(self.visible_checkbox, 1, wx.EXPAND) 
        sizer.Add(symbol_static, 0, 
                  wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 
                  5)
        sizer.Add(self.symbol_btn, 1, wx.EXPAND)
        sizer.Add(desc_static, 0, wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 5)
        sizer.Add(self.desc_txt, 1,  wx.EXPAND)
        
        panel.SetSizer(sizer)
        
        self.Bind(wx.EVT_BUTTON, self.on_editsymbol, self.symbol_btn)
        
    
       
class SymbolEditorDialog(wx.Frame):    
    '''
    Symbol editor frame allowing the user to select a shape,
    a border color, a filling color and a size
    '''
    
    def __init__(self, parent, objecttype):
        wx.Frame.__init__(self, parent, -1, "Symbol editor")
        self.panel = wx.Panel(self)
       
        self.objecttype = objecttype
       
        # create controls
        self.pen_static = wx.StaticText(self.panel, -1, u"Border color")
        self.pen_btn = wx.Button(self.panel, -1, u"")
        self.pen_btn.SetBackgroundColour(self.objecttype.pen)
        
        if isinstance(self.objecttype, NodeType):
            self.brush_static = wx.StaticText(self.panel, -1, u"Fill color")
            self.brush_btn = wx.Button(self.panel, -1, u"")
            self.brush_btn.SetBackgroundColour(self.objecttype.brush)
        elif isinstance(self.objecttype, LinkType):
            self.style_static = wx.StaticText(self.panel, -1, u"Style")
            self.style_btn = wx.ComboBox(self.panel, -1, 
                                         value = self.objecttype.style,
                                         choices=["wx.SOLID", "wx.DOT", 
                                                   "wx.LONG_DASH",
                                                   "wx.SHORT_DASH"])
        
        self.size_static = wx.StaticText(self.panel, -1, u"Size")
        if isinstance(self.objecttype, NodeType):
            size_value = (self.objecttype.size is not None) \
                       and self.objecttype.size or 5
            self.size_int = IntCtrl(self.panel, -1, size_value, min=0, max=50, 
                                    limited=True, allow_none=False)
        else:
            size_value = (self.objecttype.size is not None) \
                       and self.objecttype.size or 1
            self.size_int = IntCtrl(self.panel, -1, size_value, min=0, max=5, 
                                    limited=True, allow_none=False)            
        
        if isinstance(self.objecttype, NodeType):
            self.shape_static = wx.StaticText(self.panel, -1, u"Shape")
            bmp =  renderer.getBitmapForShape(self.objecttype.shape, 
                                              self.objecttype.size)
            self.shape_btn = wx.BitmapButton(self.panel, -1, bmp)
        
        self.ok_btn = wx.Button(self.panel, -1, u"OK")
        self.cancel_btn = wx.Button(self.panel, -1, u"Cancel")
        
        
        # event binding
        if isinstance(self.objecttype, NodeType):
            self.Bind(wx.EVT_BUTTON, self.on_choosesymbol, self.shape_btn)
            self.Bind(wx.EVT_BUTTON, self.on_choosecolour, self.brush_btn)
        elif isinstance(self.objecttype, LinkType):
            self.Bind(wx.EVT_COMBOBOX, self.on_choosestyle, self.style_btn)
        self.Bind(wx.EVT_BUTTON, self.on_choosecolour, self.pen_btn)
        self.Bind(wx.EVT_BUTTON, self.update_onclose, self.ok_btn)
        self.Bind(wx.EVT_BUTTON, self.on_close, self.cancel_btn)
        self.Bind(wx.EVT_CLOSE, self.on_close)
        
        self.do_layout()

    def do_layout(self):
        '''
        Lay out the controls on the window
        '''
        self.sizer = wx.FlexGridSizer(cols=2, vgap=6, hgap=6)
        if isinstance(self.objecttype, NodeType):
            self.sizer.AddMany([self.shape_static, self.shape_btn,
                                self.pen_static, self.pen_btn,
                                self.brush_static, self.brush_btn,
                                self.size_static, self.size_int,
                                self.ok_btn, self.cancel_btn])
        else:
            self.sizer.AddMany([self.pen_static, self.pen_btn,
                                self.style_static, self.style_btn,
                                self.size_static, self.size_int,
                                self.ok_btn, self.cancel_btn])
        self.panel.SetSizer(self.sizer)

    def update_symbol(self):
        '''
        Update the objecttype attributes from what has been selected in the UI
        '''
        # it seems my wx installation does not have wx.Colour.GetAsString 
        # method !
        if isinstance(self.objecttype, NodeType):
            self.objecttype.brush = u"#%02x%02x%02x" % \
                self.brush_btn.GetBackgroundColour().Get()
            
        self.objecttype.pen = u"#%02x%02x%02x" % \
                self.pen_btn.GetBackgroundColour().Get()
        # FIXME : this should be done using a validator on the text input
        self.objecttype.size = int(self.size_int.GetValue())
        
    def update_onclose(self, event):
        '''
        Update the symbols when closing the windows
        '''
        # OnClose update the nodetype symbol definition
        self.update_symbol()
        self.GetParent().update_symbolbtn()        
        self.on_close(event)
        
    def on_close(self, event):
        '''
        Do a nice cleanup before closing
        '''
        self.Destroy()        
        
    def on_choosecolour(self, event):
        '''
        Show a colour dialog when choosing a filling or border colour
        '''
        # update background color of the button on colour selected
        colour = event.GetEventObject().GetBackgroundColour()
        colourdata = wx.ColourData().SetColour(colour)
        colourDialog = wx.ColourDialog(self, colourdata)
        colourDialog.ShowModal()        
        colourdata = colourDialog.GetColourData()
        event.GetEventObject().SetBackgroundColour(colourdata.GetColour())

    def on_choosesymbol(self, event):
        '''
        Shows the SymbolChoose dialog
        '''
        frm = SymbolChooser(self, self.objecttype)
        frm.Show()
        
    def on_choosestyle(self, event):
        """
        Callback on style choosen
        """
        self.objecttype.style = event.GetString()
    
    def update_shape(self):
        '''
        Update the shape button using the selected shape
        '''
        self.shape_btn.SetBitmapLabel(
            renderer.getBitmapForShape(self.objecttype.shape, 
                                       self.objecttype.size))
        self.Update()

class SymbolChooser(wx.Frame):
    '''
    Frame listing the available symbols for the application, so that the user 
    can choose the symbol he wants. Closes on user selection
    '''
    
    def __init__(self, parent, nodetype):
        
        wx.Frame.__init__(self, parent, -1, "Symbol choose frame")
        self.panel = wx.Panel(self)
        self.boxsizer = wx.BoxSizer(wx.VERTICAL)
        
        self.nodetype = nodetype
        
        imageList = wx.ImageList(20, 20, True, len(renderer.symbols))
        self.symbols = renderer.symbols.keys()
        self.symbols.pop(self.symbols.index("line"))
        self.symbols.sort()
        img_idx = {}
        # create the image list
        for name in self.symbols:
            img_idx[name] = imageList.Add(renderer.getBitmapForShape(name, 20))
        #create the list controller
        list_ctr = wx.ListCtrl(self.panel, -1, style=wx.LC_ICON)
        list_ctr.AssignImageList(imageList, wx.IMAGE_LIST_NORMAL)
        # fill the list with the icons and their name
        for i, name in enumerate(self.symbols):
            list_ctr.InsertImageStringItem(i, name.title(), img_idx[name]) 
            
        self.Bind(wx.EVT_CLOSE, self.on_close, self)
        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.on_select, list_ctr)
        
        self.boxsizer.Add(wx.StaticText(self.panel, -1, 
                            u"Select a shape : (double click)"), 
                          0, wx.EXPAND | wx.ALIGN_CENTER_HORIZONTAL, 5)
        self.boxsizer.Add(list_ctr, 0, wx.EXPAND | wx.ALL, 5)
        
        self.panel.SetSizer(self.boxsizer)
        #self.boxsizer.Layout()
        self.boxsizer.SetSizeHints(self.panel)
    
    def on_close(self, event):
        '''
        Do a nice cleanup when closing the window
        '''
        self.GetParent().update_shape()
        self.Destroy()
    
    def on_select(self, event):
        '''
        Update the name of the shape selected and close
        '''
        item =  event.GetItem()
        self.nodetype.shape = item.GetText().lower()
        self.on_close(event)

class DataGroupPanel(wx.Panel):
    '''
    Shows the list of data groups for a given object type. 
    
    Allowing the user to :
    - add groups
    - remove groups if no fields are linked to it
    '''
    
    def __init__(self, parent, context, otype, traitsgroups):
        
        wx.Panel.__init__(self, parent, -1)
        self.objecttype = otype
        self.context = context
        self.dbsession = self.context.session.dbsession
        
        self.tgroups = traitsgroups
        
        self.groups = [group.name for group in self.objecttype.groups]
        self.groups.sort()
        # define controls
        header_txt = wx.StaticText(self, -1,
                                   u"Existing groups for this object type")
        self.group_cb = wx.ListBox(self, -1, 
                                   choices=self.groups )
        
        self.add_group_button = wx.Button(self, -1, u"Add group")
        self.delete_group_button = wx.Button(self, -1, u"Delete group")
        
        # sizer
        hsizer = wx.BoxSizer(wx.HORIZONTAL)
        hsizer.Add((10, 10))
        hsizer.Add(self.add_group_button)
        hsizer.Add((10, 10))
        hsizer.Add(self.delete_group_button)
        hsizer.Add((10, 10))
        
        vsizer = wx.BoxSizer(wx.VERTICAL)
        vsizer.Add(header_txt, 0, wx.ALIGN_LEFT, 5)
        vsizer.Add((10, 10))
        vsizer.Add(self.group_cb, 0, wx.EXPAND, 5)
        vsizer.Add((10, 10))
        vsizer.Add(hsizer, 0, wx.ALIGN_CENTER, 15)
        vsizer.Add((10, 10))
        
        self.SetSizer(vsizer)
        
        #events
        self.Bind(wx.EVT_BUTTON, self.on_addgroup, self.add_group_button)
        self.Bind(wx.EVT_BUTTON, self.on_deletegroup, self.delete_group_button)
        
    def on_deletegroup(self, event):
        '''
        Delete group from db and from list
        '''
        # get the selected group name
        group_name = self.group_cb.GetStringSelection()
        # get the group object
        for group in self.objecttype.groups:
            if group.name == group_name:
                # check if group has some field linked to it
                if len(group.nodefields) > 0 or len(group.linkfields) > 0:
                    wx.MessageBox(u'''You cannot delete groups 
                          that are linked to node or link fields''',
                                  caption="Delete group error")
                else:                    
                    self.dbsession.delete(group)
                    self.dbsession.commit()
                    self.dbsession.refresh(self.objecttype)
                    self.tgroups.remove(str(group_name))
                    self.refresh_grouplist()
    
    def on_addgroup(self, event):
        '''
        Add group to db and to list
        '''
        dialog = wx.TextEntryDialog(self, 
                                    message=u"Name of the new data group", 
                                    caption=u"New group")
        if dialog.ShowModal() == wx.ID_OK:
            name = dialog.GetValue()
            group = FieldGroup(name)
            self.dbsession.add(group)
            self.dbsession.commit()
            self.objecttype.groups.append(group)
            self.tgroups.append(str(group.name))
            print "Group added %s" % str(self.tgroups) 
            self.refresh_grouplist()
                    
    def refresh_grouplist(self):
        '''
        Refreshes the groups list
        '''
        # clear all 
        self.group_cb.Clear()
        # add all groups
        self.groups = [group.name for group in self.objecttype.groups]
        self.groups.sort()
        self.group_cb.AppendItems(self.groups)
           
class LinksChooserPanel(wx.Panel):
    '''
    This show a panel with two list box showing all the links type. 
    
    The selected ones on the left and the available ones on the right
    '''
    
    def __init__(self, parent, context, otype):
        wx.Panel.__init__(self, parent, -1)
        
        self.nodetype = otype
        self.context = context
        
        project = context.session.hpproject
        # get all the linktypes for the loaded project
        self.available_types = project.linktypes
        self.linktypes = [l.name for l in self.available_types]
        self.linktypes.sort()
        self.selected_types = self.nodetype.linktypes
       
        self.header_txt = wx.StaticText(self, -1, 
                        u"Check the authorized link types for this node type")
        # define a check listbox control
        self.selected_list = wx.CheckListBox(self, -1, 
                                             choices=self.linktypes, 
                                             size=(250, 150), 
                                             style=wx.LB_MULTIPLE)
        for link in self.selected_types:
            # select them in the listbox
            idx = self.linktypes.index(link.name)
            self.selected_list.Check(idx, True)
      
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.header_txt)
        sizer.Add((10, 10))
        sizer.Add(self.selected_list, 1, wx.EXPAND, 5)
        self.SetSizer(sizer)
        
        self.Bind(wx.EVT_CHECKLISTBOX, self.on_checkeditem, self.selected_list)
        
    def on_checkeditem(self, event):
        '''
        A link type has been checked/unchecked
        --> apply the changes in db
        '''
        linkt_name = self.linktypes[event.GetInt()]
        is_checked = self.selected_list.IsChecked(event.GetInt())
        linktype = None
        for alinktype in self.available_types:
            if alinktype.name == linkt_name:
                linktype = alinktype 
                break
        if linktype is None: 
            return
        if is_checked is True:
            self.nodetype.linktypes.append(linktype)
            self.nodetype.modified = True
        else:
            self.nodetype.linktypes.remove(linktype)
            self.nodetype.modified = True
